package controler;

import controler.ProtocolException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import vue.components.ListItem;
import vue.panels.ChatPanel;
import vue.panels.JeuPanel;
import vue.panels.ListTablesPanel;

/**
 * <p>Title: Serveur.Protocol </p>
 *
 * <p>
 * 	Description: La classe Protocol du package Serveur  
 *  Decrit tout le protocole de communication
 * </p>
 *
 * <p>Copyright: (c) 2008 FSSIM</p>
 *
 * <p>Company: FSSImock</p>
 *
 * @author Antoine Farnault
 * @author Augustin Couette
 * @author Sebastien Oger
 * @version 1.0
 */
public class Protocol {
	
	private static final String SEPARATOR = ":";	
	
	/**
	 * Methode de parsage du protocol
	 * @param entree le flux d'entree
	 * @param from 1: from joueur    0: from serveur 
	 * @throws Exception
	 */
	public static void parser(BufferedReader entree) throws IOException, ProtocolException {
		HashMap<String, String> h = new HashMap<String, String>(); 
		String s;
		while((s=entree.readLine())!= null ){ 
			System.out.println(s);
			if (s.equals("FIN")){
				break;
			}
			try{
				Pattern pattern = Pattern.compile("^(\\w*)"+SEPARATOR+"(.*)$");
				Matcher matcher = pattern.matcher(s);
				if (matcher.find()) {
					String action = matcher.group(1);
					String values = matcher.group(2);
					h.put(action, values);
				} else {
					throw new ProtocolException("Ne peut parser la ligne", h);
				}
			}catch(Exception e){
				System.err.println("Erreur de parsing, ne peut splitter sur ':'");
				e.printStackTrace();
				return;
			}
		}
		protocolHandler(h);
	}
	
	/**
	 * Methode qui appelle les differentes fonctions de communication
	 * @param h la hashmap parse par le protocol
	 * @throws ProtocolException
	 */
	private static void protocolHandler(HashMap<String, String> h) throws ProtocolException{
		String type = h.get("type");
		
		if (type.equalsIgnoreCase("chat")){
			rcv_chat(h.get("prefix"),h.get("data"), h.get("from"),h.get("table"),h.get("couleur"));
			return;
		}
		if(type.equalsIgnoreCase("joueur")){
			rcv_joueur(h.get("pseudo"),h.get("status"), h.get("table"),h.get("couleur"));
			return;
		}
		if (type.equalsIgnoreCase("de")){
			rcv_De(h.get("score"),h.get("table"));
			return;
		}
		if(type.equalsIgnoreCase("table")){
			String status = h.get("status");
			if(status.equalsIgnoreCase("ajout")){
				boolean privee;
				if(h.get("privee").equalsIgnoreCase("true")){
					privee = true;
				} else {
					privee = false; 
				}
				rcv_table(h.get("pseudo"), h.get("nom"),h.get("theme"),Integer.parseInt(h.get("nombreJoueurs")),Integer.parseInt(h.get("capacite")),h.get("pass"),privee);
				return;
			}
			if(status.equalsIgnoreCase("join")){
				rcv_joinTable(h.get("pseudo"), h.get("table"));
				return;
			}
			if(status.equalsIgnoreCase("leave")){
				rcv_leaveTable(h.get("pseudo"), h.get("table"));
				return;
			}
		}
		if(type.equalsIgnoreCase("connect")){
			rcv_connect(h.get("status"));
			return;
		}
		if(type.equalsIgnoreCase("auth")){
			rcv_otp(h.get("seed"), h.get("sequence"));
			return;
		}
		if(type.equalsIgnoreCase("carte")){
			rcv_carte(h.get("question"), h.get("rA"), h.get("rB"), h.get("rC"), h.get("rD"), h.get("table"), h.get("pseudo"));
			return;
		}
		if(type.equalsIgnoreCase("tour")){
			rcv_tour(h.get("pseudo"), h.get("table"));
			return;
		}
		if(type.equalsIgnoreCase("reponse")){
			rcv_reponse(h.get("table"), h.get("reponse"), h.get("camembert"));
			return;
		}
		if(type.equalsIgnoreCase("case")){
			rcv_case(h.get("id"), h.get("table"), h.get("couleur"));
			return;
		}
	}
	/**
	 * On est client, on recoit la reponse du serveur par rapport au challenge auquel
	 * on a repondu.
	 * @param string
	 */
	private static void rcv_connect(String string) {
		if(string.equalsIgnoreCase("ok")){
			Joueur.ui.cp.layout.show(Joueur.ui.cp, "listTablesEtChatPanel");
	    	Joueur.ui.statutPanel.setIconConnected(true);
	    	Joueur.ui.statutPanel.setStatus("Bienvenue "+Joueur.name+" !");
	    	Joueur.status = Joueur.CONNECTED;
		} else {
			Joueur.ui.statutPanel.setStatus("Mot de passe incorrect !");
		}
	}
	
	/**
	 * On est client et on reçoit une communication de type chat
	 * @param s
	 */
	private static void rcv_chat(String prefix,String s, String from,String table,String couleur){
		boolean isFromServeur;
		if(from.equalsIgnoreCase("Serveur")){
			isFromServeur = true;
		}else{
			isFromServeur = false;
			prefix += from;
		}
		if(table.equalsIgnoreCase("salle")){
			Joueur.ui.listTablesEtChatPanel.chatPanel.displayMsg(prefix,s,Joueur.stringToColor(couleur),isFromServeur); // le chat general
		} else {
			// recherche de la bonne table
			Vector<ChatPanel> tables = Joueur.ui.listTablesEtChatPanel.tablesPanel.chatPanels; 
			for (int i = 0; i < tables.size(); i++) {
				if(tables.get(i).nomTable.equals(table)){
					tables.get(i).displayMsg(prefix,s,Joueur.stringToColor(couleur),isFromServeur); // affichage du msg dessus
				}
			}
		}
	}
	
	/**
	 * On est client et on envoie une communication de type chat
	 * @param s les donnees a envoyer
	 * @param pseudo le pseudo de qui viennent ces donnees
	 * @param table le nom de la table sur laquelle afficher les donnees
	 * @throws ProtocolException 
	 */
	public static void snd_chat(String s, String pseudo,String table,String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "chat");
		hp.put("data", s);
		hp.put("table", table);
		hp.put("couleur", couleur);
		formatToProtocol(Joueur.sortie,hp);
	}
	
	/**
	 * On est client et on envoie une demande de connexion
	 * @param name nom
	 * @param pass mot de passe
	 * @throws ProtocolException 
	 */
	public static void snd_connect(String name, String pass) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "connect");
		hp.put("pseudo",name);
		hp.put("pass",pass);
		formatToProtocol(Joueur.sortie, hp);
	}
	
	/**
	 * On est client et on recoit un joueur a mettre a jour dans la liste des joueurs 
	 * @param pseudo
	 * @param status
	 */
	private static void rcv_joueur(String pseudo, String status, String table, String couleur){
		if(pseudo.equals(Joueur.name)){ // C'est moi !
			Joueur.couleur = Joueur.stringToColor(couleur); // je set ma couleur donnee par le serveur
			Joueur.ui.listTablesEtChatPanel.chatPanel.msgText.setForeground(Joueur.couleur);
		}
		if (status.equalsIgnoreCase("connected")){
			// on rejoint la salle !
			Joueur.ui.listTablesEtChatPanel.chatPanel.addToPseudoList(pseudo,Joueur.stringToColor(couleur)); // envoie sur le chat principal
		}
		if (status.equalsIgnoreCase("disconnected")){
			Joueur.ui.listTablesEtChatPanel.chatPanel.delToPseudoList(pseudo,Joueur.stringToColor(couleur)); // envoie sur le chat principal
			Iterator<Table> it = Joueur.getTables().iterator();
			while(it.hasNext()){
				Table tmp = it.next();
				if(tmp.pseudos.contains(pseudo)){ // on enleve ce joueur de toutes les tables ou il est
					tmp.pseudos.remove(pseudo);
					tmp.nombreJoueurs--;
					ListTablesPanel.listTable.updateUI();
					Iterator<ChatPanel> itp = Joueur.ui.listTablesEtChatPanel.tablesPanel.chatPanels.iterator();
					while(itp.hasNext()){
						ChatPanel cp = itp.next();
						if(cp.nomTable.equalsIgnoreCase(tmp.nom)){
							cp.delToPseudoList(pseudo,Joueur.stringToColor(couleur));
							break;
						}
					}
				}
			}
		}

	}
	/**
	 * On est client et on envoie la notification de creation d'une nouvelle table
	 * @param nom le nom de la table
	 * @throws ProtocolException 
	 */
	public static void snd_table(String nom,String theme,int nombreJoueurs,int capacite,String pass,boolean privee) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "table");
		hp.put("status", "ajout");
		hp.put("nom", nom);
		hp.put("proprietaire", Joueur.name);
		hp.put("theme", theme);
		hp.put("nombreJoueurs", ""+nombreJoueurs);
		hp.put("capacite", ""+capacite);
		hp.put("pass", pass);
		hp.put("privee", ""+privee);
		formatToProtocol(Joueur.sortie,hp);
	}
	
	/**
	 * On est client et on recoit une table a ajouter a sa liste
	 * @param proprietaire
	 * @param nom
	 * @param theme
	 * @param nombreJoueur
	 * @param capacite
	 * @param pass
	 * @param privee
	 */
	private static void rcv_table(String proprietaire,String nom,String theme,int nombreJoueurs, int capacite,String pass,boolean privee){
		Table t = new Table(nom,theme,proprietaire,nombreJoueurs, capacite,pass,privee);
		Joueur.ui.listTablesEtChatPanel.tablesPanel.addTableToList(t);		
		//Salle.tables.add(t);
	}
	
	/**
	 * Demande la liste des joueurs
	 * @param name
	 * @param table
	 * @throws ProtocolException 
	 */
	public static void ask_listeJoueurs(String name, String table) throws ProtocolException {
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "ask_listeJoueurs");
		hp.put("pseudo",name);
		hp.put("table",table);
		formatToProtocol(Joueur.sortie, hp);
	}
	
	/**
	 * Le client annonce qu'il rejoint une table
	 * @param nom
	 * @throws ProtocolException 
	 */
	public static void snd_joinTable(String nom) throws ProtocolException {
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "table");
		hp.put("status", "join");
		hp.put("pseudo", Joueur.name);
		hp.put("table", nom);
		formatToProtocol(Joueur.sortie, hp);
	}
	
	/**
	 * Reception d'une table a mettre a jour parce que qlq un est entre dedans
	 * @param pseudo
	 * @param nomTable
	 */
	private static void rcv_joinTable(String pseudo, String nomTable){
		//int nbTable = ListTablesPanel.listTableModel.getRowCount();
		Table t = Table.getTableByName(nomTable);
		int index = Table.getTableIndexByName(nomTable);
		/*for (int j = 0; j < nbTable; j++) {
			Table t;
			try {
				t = ListTablesPanel.listTableModel.getRow(j);
				if(t.nom.equals(nomTable)){
					t.pseudos.add(pseudo);
					t.nombreJoueurs++;
					ListTablesPanel.listTableModel.setValueAt(j, t);
					ListTablesPanel.listTable.setModel(ListTablesPanel.listTableModel);
					ListTablesPanel.listTable.updateUI();
					return;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}*/
		//MAJ de la liste des tables
		t.pseudos.add(pseudo);
		t.nombreJoueurs++;
		ListTablesPanel.listTableModel.setValueAt(index, t);
		ListTablesPanel.listTable.setModel(ListTablesPanel.listTableModel);
		ListTablesPanel.listTable.updateUI();
		
		//Mise a jour de la listes des clients dans le chat de la table
		Iterator<ChatPanel> it = Joueur.ui.listTablesEtChatPanel.tablesPanel.chatPanels.iterator();
		while(it.hasNext()){
			ChatPanel cp = it.next();
			if(cp.nomTable.equalsIgnoreCase(nomTable)){
				cp.addToPseudoList(pseudo,Joueur.stringToColor("000,000,000"));//TODO
				break;
			}
		}
	}
	/**
	 * Reception d'une table a mettre a jour parce que qlq un est entre dedans
	 * @param pseudo
	 * @param nomTable
	 */
	private static void rcv_leaveTable(String pseudo, String nomTable){
		int nbTable = ListTablesPanel.listTableModel.getRowCount();
		for (int j = 0; j < nbTable; j++) {
			Table t;
			try {
				t = ListTablesPanel.listTableModel.getRow(j);
				if(t.nom.equals(nomTable)){
					t.pseudos.remove(pseudo);
					t.nombreJoueurs--;
					ListTablesPanel.listTableModel.setValueAt(j, t);
					ListTablesPanel.listTable.setModel(ListTablesPanel.listTableModel);
					ListTablesPanel.listTable.updateUI();
					return;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * Le client annonce qu'il quitte une table
	 * @param nom
	 * @throws ProtocolException 
	 */
	public static void snd_leaveTable(String nom) throws ProtocolException {
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "table");
		hp.put("status", "leave");
		hp.put("pseudo", Joueur.name);
		hp.put("table", nom);
		formatToProtocol(Joueur.sortie, hp);
	}

	/**
	 * Demande a lancer le dé
	 * @param name
	 * @param table
	 * @throws ProtocolException 
	 */
	public static void ask_lancerDe(String name, String table) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "de");
		hp.put("pseudo", name);
		hp.put("table", table);
		formatToProtocol(Joueur.sortie, hp);
	}

	/**
	 * Reception de la graine et du numero de sequence venant du serveur
	 * pour l'identification One Time Password (otp)
	 * @param seed
	 * @param sequence
	 */
	private static void rcv_otp(String seed, String sequence){
		char[] tmp = Joueur.ui.loginPanel.passTextField.getPassword();
		String password = "";
		for (int i = 0; i < tmp.length; i++) {
			password += tmp[i];
		}
		
		byte[] otp = null;
		try {
			otp = Auth.compute(seed, password, Integer.parseInt(sequence));
			String hash = new BigInteger(otp).toString(16);
			snd_otp(hash);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Envoie du challenge resolu par le client.
	 * @param hash
	 * @throws ProtocolException 
	 */
	public static void snd_otp(String hash) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "auth");
		hp.put("otp", hash);
		formatToProtocol(Joueur.sortie,hp);
	}
	
	/**
	 * Recupere le score du joueur au de
	 * @param data
	 * @param table
	 */
	public static void rcv_De(String data, String table){ 

		Integer score = new Integer(data);
		Joueur.scoreDe = score;
		Joueur.de = true;
		Vector<JeuPanel> tables = Joueur.ui.listTablesEtChatPanel.tablesPanel.jeuPanels; 
		for (int i = 0; i < tables.size(); i++) {
			if(tables.get(i).table.nom.equals(table)){
				tables.get(i).desLabel.setText(data);
				// MAJ des cases accessibles
				//tables.get(i).plateauCanvas.casesAccessibles = 
				// ICI ICI ICI TODO 
				break;
			}
		}
		
    }
	
	/**
	 * Le client recoit une question
	 * @param question
	 * @param rA
	 * @param rB
	 * @param rC
	 * @param rD
	 * @param table
	 */
	private static void rcv_carte(String question, String rA, String rB, String rC, String rD, String table, String pseudo){
		Vector<JeuPanel> jeuPanels = Joueur.ui.listTablesEtChatPanel.tablesPanel.jeuPanels; 
		for (int i = 0; i < jeuPanels.size(); i++) {
			JeuPanel j = jeuPanels.get(i);
			if(j.table.nom.equals(table)){
				j.qrText.setText(question+"\nA: "+rA+"\nB: "+rB+"\nC: "+rC+"\nD: "+rD);
				if(Joueur.name.equals(pseudo)){
					//j.progressBar.setValue(0);
					j.rAButton.setEnabled(true);
					j.rBButton.setEnabled(true);
					j.rCButton.setEnabled(true);
					j.rDButton.setEnabled(true);
					j.qrText.setOpaque(true);
				}else{
					j.qrText.setOpaque(false);
				}
				break;
			}
		}
	}
	
	private static void rcv_tour(String Pseudo, String table){
		Joueur.tour = true;
		Vector<JeuPanel> tables = Joueur.ui.listTablesEtChatPanel.tablesPanel.jeuPanels; 
		for (int i = 0; i < tables.size(); i++) {
			if(tables.get(i).table.nom.equals(table)){
				tables.get(i).lancerButton.setEnabled(true);
				break;
			}
		}
	}
	
	
	public static void snd_reponse(String reponse, String table, String camembert) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "reponse");
		hp.put("table", table);
		hp.put("reponse", reponse);
		hp.put("camembert", camembert);
		formatToProtocol(Joueur.sortie, hp);
		Joueur.tour = false;
	}
	
	private static void rcv_reponse(String table, String reponse, String camembert){
		if(reponse.equalsIgnoreCase("ok")){
			System.out.println("BONNE REPONSE");
			if(camembert.equalsIgnoreCase("true")){
				Table t = Table.getTableByName(table);
				t.ajouterClaquos();
			}
		}else{
			//TODO
			System.out.println("MAUVAISE REPONSE");
		}
	}
	
	/**
	 * Envoie les donnees dans le format du protocol en verifiant 
	 * qu'elles sont bien formees.
	 * @param p 
	 * @param hp
	 */
	public static void formatToProtocol(PrintWriter p,HashMap<String, String> hp ) throws ProtocolException {
		try{
			hp.get("type");
		}catch(Exception e){
			throw new ProtocolException("Il n'y a pas de 'type' pour cet envoie de donnees");
		}
		for(Entry<String, String> entry : hp.entrySet()) {
		    String cle = entry.getKey();
		    String valeur = entry.getValue();
		    
		    // verif sur la cle et la valeur
		    if(cle.matches(":")){ // la regexp du parser prend le 1er ':' donc il en faut pas ici
		    	throw new ProtocolException("La cle contient ':'");
		    }
		    // Ecriture des donnees
		    p.println(cle+":"+valeur);
		}
		p.println("FIN");
		p.flush();
	}

	public static void snd_case(int id, Table t) throws ProtocolException {
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "case");
		hp.put("table", t.nom);
		hp.put("id", id+"");
		formatToProtocol(Joueur.sortie, hp);
	}
	
	private static void rcv_case(String id, String table, String couleur){
		Vector<JeuPanel> jeuPanels = Joueur.ui.listTablesEtChatPanel.tablesPanel.jeuPanels; 
		for (int i = 0; i < jeuPanels.size(); i++) {
			JeuPanel jp = jeuPanels.get(i);
			if(jp.table.nom.equals(table)){
				
				for (int j = 0; j < jp.plateauCanvas.pions.size(); j++) {
					if(Joueur.colorToString(jp.plateauCanvas.pions.get(j).color).equals(couleur)){
						jp.plateauCanvas.pions.remove(j);
					}
				}
				jp.plateauCanvas.pions.add(new ListItem(Joueur.stringToColor(couleur), id));
			}
		}
	}
}



